﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace Gunningine
{
    using OpenTK;
    using OpenTK.Input;

    /// <summary>
    /// This class manages a stack of game states, and the transitions between them.
    /// </summary>
    public class GameStateManager : IDisposable
    {
        Logger logger;

        List<GameState> states = new List<GameState>();

        private ResourceManager resourceMgr;

        private RenderSystem renderSystem;

        private InputManager inputMgr;

        private ScriptManager scriptMgr;

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="initialState">state to start off in.</param>
        /// <param name="join">do we start the state at once?</param>
        public GameStateManager(ResourceManager resourceMgr, 
                                RenderSystem renderSystem, 
                                InputManager inputMgr, 
                                ScriptManager scriptMgr, 
                                GameState initialState = null, 
                                bool join = false)
        {
            this.resourceMgr = resourceMgr;
            this.renderSystem = renderSystem;
            this.inputMgr = inputMgr;
            this.scriptMgr = scriptMgr;

            if (initialState != null)
                MoveTo(initialState, join);
        }

        /// <summary>
        /// Move to a new state, suspending the previous one.
        /// </summary>
        /// <param name="newState">state instance.</param>
        /// <param name="join">should we start this state at once?</param>
        public void MoveTo(GameState newState, bool join = true)
        {
            newState.InitialiseManagers(
                this.resourceMgr, 
                this, 
                this.renderSystem, 
                this.inputMgr, 
                this.scriptMgr);

            this.states.Add(newState);

            if (join)
                Join();
        }

        /// <summary>
        /// Start current state.  Only call this if you have moved to a start without joining it.
        /// In other words, this is for delayed execution.
        /// </summary>
        public void Join()
        {
            Debug.Assert(this.states.Count > 0, "There is no state to join!");

            GameState curState = this.states.Count > 1 ? this.states[this.states.Count - 2] : null;
            GameState newState = this.states[this.states.Count - 1];

            if (curState != null)
                curState.Suspend(newState);

            newState.Enter(curState);
        }

        /// <summary>
        /// Exit current state, resuming the previous one.
        /// </summary>
        public void ExitCurrent()
        {
            Debug.Assert(this.states.Count > 0, "There is no state to exit from!");

            GameState curState = this.states[this.states.Count - 1];
            GameState newState = this.states.Count > 1 ? this.states[this.states.Count - 2] : null;

            this.states.Remove(curState);

            curState.Exit();

            if (newState != null)
                newState.Resume(curState);
            else
                throw new ExitException(true);
        }

        /// <summary>
        /// Update the current state.
        /// </summary>
        /// <param name="frameTime">time since last update, in seconds.</param>
        /// <param name="inputMgr">input manager to query for input.</param>
        public void Update(float frameTime, InputManager inputMgr)
        {
            Debug.Assert(this.states.Count > 0, "There is no state to update!");

            try
            {
                this.states[this.states.Count - 1].Update(frameTime, inputMgr);
            }
            catch (ChangeStateException e)
            {
                if (e.NextState != null)
                {
                    MoveTo(e.NextState);
                }
                else
                {
                    // ExitCurrent throws an exception when exiting its last
                    // state, so this will not raise hell.
                    while (true)
                        ExitCurrent();
                }
            }
        }

        /// <summary>
        /// Render the current state.
        /// </summary>
        /// <param name="renderSystem">render system to use for rendering.</param>
        public void Render(RenderSystem renderSystem)
        {
            Debug.Assert(this.states.Count > 0, "There is no state to render!");
            this.states[this.states.Count - 1].Render(renderSystem);
        }

        #region IDisposable implementation

        bool _disposed = false;

        public void Dispose()
        {
            // TODO: delete textures
            // ...

            if (!_disposed)
            {
                this._disposed = true;
            }
        }

        #endregion

    }
}